
import { MathUtil } from "./MathUtil";
import { Mat3 } from "./Mat3";
import { M } from "./M";
import { Vec3 } from "./Vec3";
import { Transform } from "./Transform";

/**
 * 4x4 Matrix class.
 *
 * Note that columns and rows are 0-indexed.
 */
export class Mat4 {
	/**
	 * The number of instance creation.
	 */
	public static numCreations: number = 0;

	/**
	 * The element at row 0 column 0.
	 */
	e00: number;

	/**
	 * The element at row 0 column 1.
	 */
	e01: number;

	/**
	 * The element at row 0 column 2.
	 */
	e02: number;

	/**
	 * The element at row 0 column 3.
	 */
	e03: number;

	/**
	 * The element at row 1 column 0.
	 */
	e10: number;

	/**
	 * The element at row 1 column 1.
	 */
	e11: number;

	/**
	 * The element at row 1 column 2.
	 */
	e12: number;

	/**
	 * The element at row 1 column 3.
	 */
	e13: number;

	/**
	 * The element at row 2 column 0.
	 */
	e20: number;

	/**
	 * The element at row 2 column 1.
	 */
	e21: number;

	/**
	 * The element at row 2 column 2.
	 */
	e22: number;

	/**
	 * The element at row 2 column 3.
	 */
	e23: number;

	/**
	 * The element at row 3 column 0.
	 */
	e30: number;

	/**
	 * The element at row 3 column 1.
	 */
	e31: number;

	/**
	 * The element at row 3 column 2.
	 */
	e32: number;

	/**
	 * The element at row 3 column 3.
	 */
	e33: number;

	/**
	 * Creates a new matrix. The matrix is identity by default.
	 */
	constructor(
		e00: number = 1, e01: number = 0, e02: number = 0, e03: number = 0,
		e10: number = 0, e11: number = 1, e12: number = 0, e13: number = 0,
		e20: number = 0, e21: number = 0, e22: number = 1, e23: number = 0,
		e30: number = 0, e31: number = 0, e32: number = 0, e33: number = 1
	) {
		this.e00 = e00;
		this.e01 = e01;
		this.e02 = e02;
		this.e03 = e03;
		this.e10 = e10;
		this.e11 = e11;
		this.e12 = e12;
		this.e13 = e13;
		this.e20 = e20;
		this.e21 = e21;
		this.e22 = e22;
		this.e23 = e23;
		this.e30 = e30;
		this.e31 = e31;
		this.e32 = e32;
		this.e33 = e33;
		Mat4.numCreations++;
	}


	private _init(
		e00: number, e01: number, e02: number, e03: number,
		e10: number, e11: number, e12: number, e13: number,
		e20: number, e21: number, e22: number, e23: number,
		e30: number, e31: number, e32: number, e33: number
	): Mat4 {
		var t00: number = e00;
		var t01: number = e01;
		var t02: number = e02;
		var t03: number = e03;
		var t10: number = e10;
		var t11: number = e11;
		var t12: number = e12;
		var t13: number = e13;
		var t20: number = e20;
		var t21: number = e21;
		var t22: number = e22;
		var t23: number = e23;
		var t30: number = e30;
		var t31: number = e31;
		var t32: number = e32;
		var t33: number = e33;
		this.e00 = t00;
		this.e01 = t01;
		this.e02 = t02;
		this.e03 = t03;
		this.e10 = t10;
		this.e11 = t11;
		this.e12 = t12;
		this.e13 = t13;
		this.e20 = t20;
		this.e21 = t21;
		this.e22 = t22;
		this.e23 = t23;
		this.e30 = t30;
		this.e31 = t31;
		this.e32 = t32;
		this.e33 = t33;
		return this;
	}

	// --- public ---

	/**
	 * Sets all elements at once and returns `this`.
	 */
	public init(
		e00: number, e01: number, e02: number, e03: number,
		e10: number, e11: number, e12: number, e13: number,
		e20: number, e21: number, e22: number, e23: number,
		e30: number, e31: number, e32: number, e33: number
	): Mat4 {
		this.e00 = e00;
		this.e01 = e01;
		this.e02 = e02;
		this.e03 = e03;
		this.e10 = e10;
		this.e11 = e11;
		this.e12 = e12;
		this.e13 = e13;
		this.e20 = e20;
		this.e21 = e21;
		this.e22 = e22;
		this.e23 = e23;
		this.e30 = e30;
		this.e31 = e31;
		this.e32 = e32;
		this.e33 = e33;
		return this;
	}

	/**
	 * Sets this matrix to identity matrix and returns `this`.
	 */
	public identity(): Mat4 {
		this._init(
			1, 0, 0, 0,
			0, 1, 0, 0,
			0, 0, 1, 0,
			0, 0, 0, 1
		);
		return this;
	}

	/**
	 * Returns `this` + `m`
	 */
	public add(m: Mat4): Mat4 {
		return new Mat4(
			this.e00 + m.e00, this.e01 + m.e01, this.e02 + m.e02, this.e03 + m.e03,
			this.e10 + m.e10, this.e11 + m.e11, this.e12 + m.e12, this.e13 + m.e13,
			this.e20 + m.e20, this.e21 + m.e21, this.e22 + m.e22, this.e23 + m.e23,
			this.e30 + m.e30, this.e31 + m.e31, this.e32 + m.e32, this.e33 + m.e33
		);
	}

	/**
	 * Returns `this` - `m`
	 */
	public sub(m: Mat4): Mat4 {
		return new Mat4(
			this.e00 - m.e00, this.e01 - m.e01, this.e02 - m.e02, this.e03 - m.e03,
			this.e10 - m.e10, this.e11 - m.e11, this.e12 - m.e12, this.e13 - m.e13,
			this.e20 - m.e20, this.e21 - m.e21, this.e22 - m.e22, this.e23 - m.e23,
			this.e30 - m.e30, this.e31 - m.e31, this.e32 - m.e32, this.e33 - m.e33
		);
	}

	/**
	 * Returns `this` * `s`
	 */
	public scale(s: number): Mat4 {
		return new Mat4(
			this.e00 * s, this.e01 * s, this.e02 * s, this.e03 * s,
			this.e10 * s, this.e11 * s, this.e12 * s, this.e13 * s,
			this.e20 * s, this.e21 * s, this.e22 * s, this.e23 * s,
			this.e30 * s, this.e31 * s, this.e32 * s, this.e33 * s
		);
	}

	/**
	 * Returns `this` * `m`
	 */
	public mul(m: Mat4): Mat4 {
		return new Mat4(
			this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20 + this.e03 * m.e30,
			this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21 + this.e03 * m.e31,
			this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22 + this.e03 * m.e32,
			this.e00 * m.e03 + this.e01 * m.e13 + this.e02 * m.e23 + this.e03 * m.e33,
			this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20 + this.e13 * m.e30,
			this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21 + this.e13 * m.e31,
			this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22 + this.e13 * m.e32,
			this.e10 * m.e03 + this.e11 * m.e13 + this.e12 * m.e23 + this.e13 * m.e33,
			this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20 + this.e23 * m.e30,
			this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21 + this.e23 * m.e31,
			this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22 + this.e23 * m.e32,
			this.e20 * m.e03 + this.e21 * m.e13 + this.e22 * m.e23 + this.e23 * m.e33,
			this.e30 * m.e00 + this.e31 * m.e10 + this.e32 * m.e20 + this.e33 * m.e30,
			this.e30 * m.e01 + this.e31 * m.e11 + this.e32 * m.e21 + this.e33 * m.e31,
			this.e30 * m.e02 + this.e31 * m.e12 + this.e32 * m.e22 + this.e33 * m.e32,
			this.e30 * m.e03 + this.e31 * m.e13 + this.e32 * m.e23 + this.e33 * m.e33
		);
	}

	/**
	 * Sets this matrix to `this` + `m` and returns `this`.
	 */
	public addEq(m: Mat4): Mat4 {
		return this._init(
			this.e00 + m.e00, this.e01 + m.e01, this.e02 + m.e02, this.e03 + m.e03,
			this.e10 + m.e10, this.e11 + m.e11, this.e12 + m.e12, this.e13 + m.e13,
			this.e20 + m.e20, this.e21 + m.e21, this.e22 + m.e22, this.e23 + m.e23,
			this.e30 + m.e30, this.e31 + m.e31, this.e32 + m.e32, this.e33 + m.e33
		);
	}

	/**
	 * Sets this matrix to `this` - `m` and returns `this`.
	 */
	public subEq(m: Mat4): Mat4 {
		return this._init(
			this.e00 - m.e00, this.e01 - m.e01, this.e02 - m.e02, this.e03 - m.e03,
			this.e10 - m.e10, this.e11 - m.e11, this.e12 - m.e12, this.e13 - m.e13,
			this.e20 - m.e20, this.e21 - m.e21, this.e22 - m.e22, this.e23 - m.e23,
			this.e30 - m.e30, this.e31 - m.e31, this.e32 - m.e32, this.e33 - m.e33
		);
	}

	/**
	 * Sets this matrix to `this` * `s` and returns `this`.
	 */
	public scaleEq(s: number): Mat4 {
		return this._init(
			this.e00 * s, this.e01 * s, this.e02 * s, this.e03 * s,
			this.e10 * s, this.e11 * s, this.e12 * s, this.e13 * s,
			this.e20 * s, this.e21 * s, this.e22 * s, this.e23 * s,
			this.e30 * s, this.e31 * s, this.e32 * s, this.e33 * s
		);
	}

	/**
	 * Sets this matrix to `this` * `m` and returns `this`.
	 */
	public mulEq(m: Mat4): Mat4 {
		return this._init(
			this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20 + this.e03 * m.e30,
			this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21 + this.e03 * m.e31,
			this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22 + this.e03 * m.e32,
			this.e00 * m.e03 + this.e01 * m.e13 + this.e02 * m.e23 + this.e03 * m.e33,
			this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20 + this.e13 * m.e30,
			this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21 + this.e13 * m.e31,
			this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22 + this.e13 * m.e32,
			this.e10 * m.e03 + this.e11 * m.e13 + this.e12 * m.e23 + this.e13 * m.e33,
			this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20 + this.e23 * m.e30,
			this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21 + this.e23 * m.e31,
			this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22 + this.e23 * m.e32,
			this.e20 * m.e03 + this.e21 * m.e13 + this.e22 * m.e23 + this.e23 * m.e33,
			this.e30 * m.e00 + this.e31 * m.e10 + this.e32 * m.e20 + this.e33 * m.e30,
			this.e30 * m.e01 + this.e31 * m.e11 + this.e32 * m.e21 + this.e33 * m.e31,
			this.e30 * m.e02 + this.e31 * m.e12 + this.e32 * m.e22 + this.e33 * m.e32,
			this.e30 * m.e03 + this.e31 * m.e13 + this.e32 * m.e23 + this.e33 * m.e33
		);
	}

	/**
	 * Returns *scaling matrix* * `this`.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public prependScale(sx: number, sy: number, sz: number): Mat4 {
		return new Mat4(
			this.e00 * sx, this.e01 * sx, this.e02 * sx, this.e03 * sx,
			this.e10 * sy, this.e11 * sy, this.e12 * sy, this.e13 * sy,
			this.e20 * sz, this.e21 * sz, this.e22 * sz, this.e23 * sz,
			this.e30, this.e31, this.e32, this.e33
		);
	}

	/**
	 * Returns `this` * *scaling matrix*.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public appendScale(sx: number, sy: number, sz: number): Mat4 {
		return new Mat4(
			this.e00 * sx, this.e01 * sy, this.e02 * sz, this.e03,
			this.e10 * sx, this.e11 * sy, this.e12 * sz, this.e13,
			this.e20 * sx, this.e21 * sy, this.e22 * sz, this.e23,
			this.e30 * sx, this.e31 * sy, this.e32 * sz, this.e33
		);
	}

	/**
	 * Returns *rotation matrix* * `this`.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public prependRotation(rad: number, axisX: number, axisY: number, axisZ: number): Mat4 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return new Mat4(
			r00 * this.e00 + r01 * this.e10 + r02 * this.e20,
			r00 * this.e01 + r01 * this.e11 + r02 * this.e21,
			r00 * this.e02 + r01 * this.e12 + r02 * this.e22,
			r00 * this.e03 + r01 * this.e13 + r02 * this.e23,
			r10 * this.e00 + r11 * this.e10 + r12 * this.e20,
			r10 * this.e01 + r11 * this.e11 + r12 * this.e21,
			r10 * this.e02 + r11 * this.e12 + r12 * this.e22,
			r10 * this.e03 + r11 * this.e13 + r12 * this.e23,
			r20 * this.e00 + r21 * this.e10 + r22 * this.e20,
			r20 * this.e01 + r21 * this.e11 + r22 * this.e21,
			r20 * this.e02 + r21 * this.e12 + r22 * this.e22,
			r20 * this.e03 + r21 * this.e13 + r22 * this.e23,
			this.e30, this.e31, this.e32, this.e33
		);
	}

	/**
	 * Returns `this` * *rotation matrix*.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public appendRotation(rad: number, axisX: number, axisY: number, axisZ: number): Mat4 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return new Mat4(
			this.e00 * r00 + this.e01 * r10 + this.e02 * r20,
			this.e00 * r01 + this.e01 * r11 + this.e02 * r21,
			this.e00 * r02 + this.e01 * r12 + this.e02 * r22,
			this.e03,
			this.e10 * r00 + this.e11 * r10 + this.e12 * r20,
			this.e10 * r01 + this.e11 * r11 + this.e12 * r21,
			this.e10 * r02 + this.e11 * r12 + this.e12 * r22,
			this.e13,
			this.e20 * r00 + this.e21 * r10 + this.e22 * r20,
			this.e20 * r01 + this.e21 * r11 + this.e22 * r21,
			this.e20 * r02 + this.e21 * r12 + this.e22 * r22,
			this.e23,
			this.e30 * r00 + this.e31 * r10 + this.e32 * r20,
			this.e30 * r01 + this.e31 * r11 + this.e32 * r21,
			this.e30 * r02 + this.e31 * r12 + this.e32 * r22,
			this.e33
		);
	}

	/**
	 * Returns *translation matrix* * `this`.
	 *
	 * Where *translation matrix* is a matrix which translates `sx`, `sy` and `sz` along
	 * the x-axis, y-axis and z-axis respectively.
	 */
	public prependTranslation(tx: number, ty: number, tz: number): Mat4 {
		return new Mat4(
			this.e00 + tx * this.e30, this.e01 + tx * this.e31, this.e02 + tx * this.e32, this.e03 + tx * this.e33,
			this.e10 + ty * this.e30, this.e11 + ty * this.e31, this.e12 + ty * this.e32, this.e13 + ty * this.e33,
			this.e20 + tz * this.e30, this.e21 + tz * this.e31, this.e22 + tz * this.e32, this.e23 + tz * this.e33,
			this.e30, this.e31, this.e32, this.e33
		);
	}

	/**
	 * Returns `this` * *translation matrix*.
	 *
	 * Where *translation matrix* is a matrix which translates `sx`, `sy` and `sz` along
	 * the x-axis, y-axis and z-axis respectively.
	 */
	public appendTranslation(tx: number, ty: number, tz: number): Mat4 {
		return new Mat4(
			this.e00, this.e01, this.e02, this.e00 * tx + this.e01 * ty + this.e02 * tz + this.e03,
			this.e10, this.e11, this.e12, this.e10 * tx + this.e11 * ty + this.e12 * tz + this.e13,
			this.e20, this.e21, this.e22, this.e20 * tx + this.e21 * ty + this.e22 * tz + this.e23,
			this.e30, this.e31, this.e32, this.e30 * tx + this.e31 * ty + this.e32 * tz + this.e33
		);
	}

	/**
	 * Sets this matrix to *scaling matrix* * `this`, and returns `this`.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public prependScaleEq(sx: number, sy: number, sz: number): Mat4 {
		return this._init(
			this.e00 * sx, this.e01 * sx, this.e02 * sx, this.e03 * sx,
			this.e10 * sy, this.e11 * sy, this.e12 * sy, this.e13 * sy,
			this.e20 * sz, this.e21 * sz, this.e22 * sz, this.e23 * sz,
			this.e30, this.e31, this.e32, this.e33
		);
	}

	/**
	 * Sets this matrix to `this` * *scaling matrix*, and returns `this`.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public appendScaleEq(sx: number, sy: number, sz: number): Mat4 {
		return this._init(
			this.e00 * sx, this.e01 * sy, this.e02 * sz, this.e03,
			this.e10 * sx, this.e11 * sy, this.e12 * sz, this.e13,
			this.e20 * sx, this.e21 * sy, this.e22 * sz, this.e23,
			this.e30 * sx, this.e31 * sy, this.e32 * sz, this.e33
		);
	}

	/**
	 * Sets this matrix to *rotation matrix* * `this`, and returns `this`.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public prependRotationEq(rad: number, axisX: number, axisY: number, axisZ: number): Mat4 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return this._init(
			r00 * this.e00 + r01 * this.e10 + r02 * this.e20, r00 * this.e01 + r01 * this.e11 + r02 * this.e21, r00 * this.e02 + r01 * this.e12 + r02 * this.e22, r00 * this.e03 + r01 * this.e13 + r02 * this.e23,
			r10 * this.e00 + r11 * this.e10 + r12 * this.e20, r10 * this.e01 + r11 * this.e11 + r12 * this.e21, r10 * this.e02 + r11 * this.e12 + r12 * this.e22, r10 * this.e03 + r11 * this.e13 + r12 * this.e23,
			r20 * this.e00 + r21 * this.e10 + r22 * this.e20, r20 * this.e01 + r21 * this.e11 + r22 * this.e21, r20 * this.e02 + r21 * this.e12 + r22 * this.e22, r20 * this.e03 + r21 * this.e13 + r22 * this.e23,
			this.e30, this.e31, this.e32, this.e33
		);
	}

	/**
	 * Sets this matrix to `this` * *rotation matrix*, and returns `this`.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public appendRotationEq(rad: number, axisX: number, axisY: number, axisZ: number): Mat4 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return this._init(
			this.e00 * r00 + this.e01 * r10 + this.e02 * r20, this.e00 * r01 + this.e01 * r11 + this.e02 * r21, this.e00 * r02 + this.e01 * r12 + this.e02 * r22, this.e03,
			this.e10 * r00 + this.e11 * r10 + this.e12 * r20, this.e10 * r01 + this.e11 * r11 + this.e12 * r21, this.e10 * r02 + this.e11 * r12 + this.e12 * r22, this.e13,
			this.e20 * r00 + this.e21 * r10 + this.e22 * r20, this.e20 * r01 + this.e21 * r11 + this.e22 * r21, this.e20 * r02 + this.e21 * r12 + this.e22 * r22, this.e23,
			this.e30 * r00 + this.e31 * r10 + this.e32 * r20, this.e30 * r01 + this.e31 * r11 + this.e32 * r21, this.e30 * r02 + this.e31 * r12 + this.e32 * r22, this.e33
		);
	}

	/**
	 * Sets this matrix to *translation matrix* * `this`, and returns `this`.
	 *
	 * Where *translation matrix* is a matrix which translates `sx`, `sy` and `sz` along
	 * the x-axis, y-axis and z-axis respectively.
	 */
	public prependTranslationEq(tx: number, ty: number, tz: number): Mat4 {
		return this._init(
			this.e00 + tx * this.e30, this.e01 + tx * this.e31, this.e02 + tx * this.e32, this.e03 + tx * this.e33,
			this.e10 + ty * this.e30, this.e11 + ty * this.e31, this.e12 + ty * this.e32, this.e13 + ty * this.e33,
			this.e20 + tz * this.e30, this.e21 + tz * this.e31, this.e22 + tz * this.e32, this.e23 + tz * this.e33,
			this.e30, this.e31, this.e32, this.e33
		);
	}

	/**
	 * Sets this matrix to `this` * *translation matrix*, and returns `this`.
	 *
	 * Where *translation matrix* is a matrix which translates `sx`, `sy` and `sz` along
	 * the x-axis, y-axis and z-axis respectively.
	 */
	public appendTranslationEq(tx: number, ty: number, tz: number): Mat4 {
		return this._init(
			this.e00, this.e01, this.e02, this.e00 * tx + this.e01 * ty + this.e02 * tz + this.e03,
			this.e10, this.e11, this.e12, this.e10 * tx + this.e11 * ty + this.e12 * tz + this.e13,
			this.e20, this.e21, this.e22, this.e20 * tx + this.e21 * ty + this.e22 * tz + this.e23,
			this.e30, this.e31, this.e32, this.e30 * tx + this.e31 * ty + this.e32 * tz + this.e33
		);
	}

	/**
	 * Returns the transposed matrix.
	 */
	public transpose(): Mat4 {
		return new Mat4(
			this.e00, this.e10, this.e20, this.e30,
			this.e01, this.e11, this.e21, this.e31,
			this.e02, this.e12, this.e22, this.e32,
			this.e03, this.e13, this.e23, this.e33
		);
	}

	/**
	 * Sets this matrix to the transposed matrix and returns `this`.
	 */
	public transposeEq(): Mat4 {
		return this._init(
			this.e00, this.e10, this.e20, this.e30,
			this.e01, this.e11, this.e21, this.e31,
			this.e02, this.e12, this.e22, this.e32,
			this.e03, this.e13, this.e23, this.e33
		);
	}

	/**
	 * Returns the determinant.
	 */
	public determinant(): number {
		var d23_01: number = this.e20 * this.e31 - this.e21 * this.e30;
		var d23_02: number = this.e20 * this.e32 - this.e22 * this.e30;
		var d23_03: number = this.e20 * this.e33 - this.e23 * this.e30;
		var d23_12: number = this.e21 * this.e32 - this.e22 * this.e31;
		var d23_13: number = this.e21 * this.e33 - this.e23 * this.e31;
		var d23_23: number = this.e22 * this.e33 - this.e23 * this.e32;
		return
		this.e00 * (this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12) -
			this.e01 * (this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02) +
			this.e02 * (this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01) -
			this.e03 * (this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01)
			;
	}

	/**
	 * Returns the trace.
	 */
	public trace(): number {
		return this.e00 + this.e11 + this.e22 + this.e33;
	}

	/**
	 * Returns the inverse matrix.
	 *
	 * If the determinant is zero, zero matrix is returned.
	 */
	public inverse(): Mat4 {
		var d01_01: number = this.e00 * this.e11 - this.e01 * this.e10;
		var d01_02: number = this.e00 * this.e12 - this.e02 * this.e10;
		var d01_03: number = this.e00 * this.e13 - this.e03 * this.e10;
		var d01_12: number = this.e01 * this.e12 - this.e02 * this.e11;
		var d01_13: number = this.e01 * this.e13 - this.e03 * this.e11;
		var d01_23: number = this.e02 * this.e13 - this.e03 * this.e12;
		var d23_01: number = this.e20 * this.e31 - this.e21 * this.e30;
		var d23_02: number = this.e20 * this.e32 - this.e22 * this.e30;
		var d23_03: number = this.e20 * this.e33 - this.e23 * this.e30;
		var d23_12: number = this.e21 * this.e32 - this.e22 * this.e31;
		var d23_13: number = this.e21 * this.e33 - this.e23 * this.e31;
		var d23_23: number = this.e22 * this.e33 - this.e23 * this.e32;
		var d00: number = this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12;
		var d01: number = this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02;
		var d02: number = this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01;
		var d03: number = this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01;
		var d10: number = this.e01 * d23_23 - this.e02 * d23_13 + this.e03 * d23_12;
		var d11: number = this.e00 * d23_23 - this.e02 * d23_03 + this.e03 * d23_02;
		var d12: number = this.e00 * d23_13 - this.e01 * d23_03 + this.e03 * d23_01;
		var d13: number = this.e00 * d23_12 - this.e01 * d23_02 + this.e02 * d23_01;
		var d20: number = this.e31 * d01_23 - this.e32 * d01_13 + this.e33 * d01_12;
		var d21: number = this.e30 * d01_23 - this.e32 * d01_03 + this.e33 * d01_02;
		var d22: number = this.e30 * d01_13 - this.e31 * d01_03 + this.e33 * d01_01;
		var d23: number = this.e30 * d01_12 - this.e31 * d01_02 + this.e32 * d01_01;
		var d30: number = this.e21 * d01_23 - this.e22 * d01_13 + this.e23 * d01_12;
		var d31: number = this.e20 * d01_23 - this.e22 * d01_03 + this.e23 * d01_02;
		var d32: number = this.e20 * d01_13 - this.e21 * d01_03 + this.e23 * d01_01;
		var d33: number = this.e20 * d01_12 - this.e21 * d01_02 + this.e22 * d01_01;
		var invDet: number = this.e00 * d00 - this.e01 * d01 + this.e02 * d02 - this.e03 * d03;
		if (invDet != 0) invDet = 1 / invDet;
		return new Mat4(
			d00 * invDet, -d10 * invDet, d20 * invDet, -d30 * invDet,
			-d01 * invDet, d11 * invDet, -d21 * invDet, d31 * invDet,
			d02 * invDet, -d12 * invDet, d22 * invDet, -d32 * invDet,
			-d03 * invDet, d13 * invDet, -d23 * invDet, d33 * invDet
		);
	}

	/**
	 * Sets this matrix to the inverse matrix and returns `this`.
	 *
	 * If the determinant is zero, this matrix is set to zero matrix.
	 */
	public inverseEq(): Mat4 {
		var d01_01: number = this.e00 * this.e11 - this.e01 * this.e10;
		var d01_02: number = this.e00 * this.e12 - this.e02 * this.e10;
		var d01_03: number = this.e00 * this.e13 - this.e03 * this.e10;
		var d01_12: number = this.e01 * this.e12 - this.e02 * this.e11;
		var d01_13: number = this.e01 * this.e13 - this.e03 * this.e11;
		var d01_23: number = this.e02 * this.e13 - this.e03 * this.e12;
		var d23_01: number = this.e20 * this.e31 - this.e21 * this.e30;
		var d23_02: number = this.e20 * this.e32 - this.e22 * this.e30;
		var d23_03: number = this.e20 * this.e33 - this.e23 * this.e30;
		var d23_12: number = this.e21 * this.e32 - this.e22 * this.e31;
		var d23_13: number = this.e21 * this.e33 - this.e23 * this.e31;
		var d23_23: number = this.e22 * this.e33 - this.e23 * this.e32;
		var d00: number = this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12;
		var d01: number = this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02;
		var d02: number = this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01;
		var d03: number = this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01;
		var d10: number = this.e01 * d23_23 - this.e02 * d23_13 + this.e03 * d23_12;
		var d11: number = this.e00 * d23_23 - this.e02 * d23_03 + this.e03 * d23_02;
		var d12: number = this.e00 * d23_13 - this.e01 * d23_03 + this.e03 * d23_01;
		var d13: number = this.e00 * d23_12 - this.e01 * d23_02 + this.e02 * d23_01;
		var d20: number = this.e31 * d01_23 - this.e32 * d01_13 + this.e33 * d01_12;
		var d21: number = this.e30 * d01_23 - this.e32 * d01_03 + this.e33 * d01_02;
		var d22: number = this.e30 * d01_13 - this.e31 * d01_03 + this.e33 * d01_01;
		var d23: number = this.e30 * d01_12 - this.e31 * d01_02 + this.e32 * d01_01;
		var d30: number = this.e21 * d01_23 - this.e22 * d01_13 + this.e23 * d01_12;
		var d31: number = this.e20 * d01_23 - this.e22 * d01_03 + this.e23 * d01_02;
		var d32: number = this.e20 * d01_13 - this.e21 * d01_03 + this.e23 * d01_01;
		var d33: number = this.e20 * d01_12 - this.e21 * d01_02 + this.e22 * d01_01;
		var invDet: number = this.e00 * d00 - this.e01 * d01 + this.e02 * d02 - this.e03 * d03;
		if (invDet != 0) invDet = 1 / invDet;
		return this._init(
			d00 * invDet, -d10 * invDet, d20 * invDet, -d30 * invDet,
			-d01 * invDet, d11 * invDet, -d21 * invDet, d31 * invDet,
			d02 * invDet, -d12 * invDet, d22 * invDet, -d32 * invDet,
			-d03 * invDet, d13 * invDet, -d23 * invDet, d33 * invDet
		);
	}

	/**
	 * Sets this matrix to *view matrix* and returns `this`.
	 *
	 * Where *view matrix* is a matrix which represents the viewing transformation with
	 * eyes at (`eyeX`, `eyeY`, `eyeZ`), fixation point at (`atX`, `atY`, `atZ`), and
	 * up vector (`upX`, `upY`, `upZ`).
	 */
	public lookAt(eyeX: number, eyeY: number, eyeZ: number, atX: number, atY: number, atZ: number, upX: number, upY: number, upZ: number): Mat4 {
		var zx: number = eyeX - atX;
		var zy: number = eyeY - atY;
		var zz: number = eyeZ - atZ;
		var tmp: number = 1 / MathUtil.sqrt(zx * zx + zy * zy + zz * zz);
		zx *= tmp;
		zy *= tmp;
		zz *= tmp;
		var xx: number = upY * zz - upZ * zy;
		var xy: number = upZ * zx - upX * zz;
		var xz: number = upX * zy - upY * zx;
		tmp = 1 / MathUtil.sqrt(xx * xx + xy * xy + xz * xz);
		xx *= tmp;
		xy *= tmp;
		xz *= tmp;
		var yx: number = zy * xz - zz * xy;
		var yy: number = zz * xx - zx * xz;
		var yz: number = zx * xy - zy * xx;
		this.e00 = xx;
		this.e01 = xy;
		this.e02 = xz;
		this.e03 = -(xx * eyeX + xy * eyeY + xz * eyeZ);
		this.e10 = yx;
		this.e11 = yy;
		this.e12 = yz;
		this.e13 = -(yx * eyeX + yy * eyeY + yz * eyeZ);
		this.e20 = zx;
		this.e21 = zy;
		this.e22 = zz;
		this.e23 = -(zx * eyeX + zy * eyeY + zz * eyeZ);
		this.e30 = 0;
		this.e31 = 0;
		this.e32 = 0;
		this.e33 = 1;
		return this;
	}

	/**
	 * Sets this matrix to *perspecive projection matrix* and returns `this`.
	 *
	 * Where *perspecive projection matrix* is a matrix which represents the perspective
	 * projection transformation with field of view in the y direction `fovY` in radians,
	 * aspect ratio `aspect`, and z-value of near and far clipping plane `near`, `far`.
	 */
	public perspective(fovY: number, aspect: number, near: number, far: number): Mat4 {
		var h: number = 1 / MathUtil.tan(fovY * 0.5);
		var fnf: number = far / (near - far);
		this.e00 = h / aspect;
		this.e01 = 0;
		this.e02 = 0;
		this.e03 = 0;
		this.e10 = 0;
		this.e11 = h;
		this.e12 = 0;
		this.e13 = 0;
		this.e20 = 0;
		this.e21 = 0;
		this.e22 = fnf;
		this.e23 = near * fnf;
		this.e30 = 0;
		this.e31 = 0;
		this.e32 = -1;
		this.e33 = 0;
		return this;
	}

	/**
	 * Sets this matrix to *orthogonal projection matrix* and returns `this`.
	 *
	 * Where *orthogonal projection matrix* is a matrix which represents the orthogonal
	 * projection transformation with screen width and height `width`, `height`, and
	 * z-value of near and far clipping plane `near`, `far`.
	 */
	public ortho(width: number, height: number, near: number, far: number): Mat4 {
		var nf: number = 1 / (near - far);
		this.e00 = 2 / width;
		this.e01 = 0;
		this.e02 = 0;
		this.e03 = 0;
		this.e10 = 0;
		this.e11 = 2 / height;
		this.e12 = 0;
		this.e13 = 0;
		this.e20 = 0;
		this.e21 = 0;
		this.e22 = nf;
		this.e23 = near * nf;
		this.e30 = 0;
		this.e31 = 0;
		this.e32 = 0;
		this.e33 = 1;
		return this;
	}

	/**
	 * Returns an array of the elements of this matrix.
	 *
	 * If `columnMajor` is true, the array is arranged in column-major order.
	 * Otherwise, the array is arranged in row-major order.
	 */
	public toArray(columnMajor: boolean = false): Array<number> {
		if (columnMajor) {
			return [
				this.e00, this.e10, this.e20, this.e30,
				this.e01, this.e11, this.e21, this.e31,
				this.e02, this.e12, this.e22, this.e32,
				this.e03, this.e13, this.e23, this.e33
			];
		} else {
			return [
				this.e00, this.e01, this.e02, this.e03,
				this.e10, this.e11, this.e12, this.e13,
				this.e20, this.e21, this.e22, this.e23,
				this.e30, this.e31, this.e32, this.e33
			];
		}
	}

	/**
	 * Copies values from `m` and returns `this`.
	 */
	public copyFrom(m: Mat4): Mat4 {
		this.e00 = m.e00;
		this.e01 = m.e01;
		this.e02 = m.e02;
		this.e03 = m.e03;
		this.e10 = m.e10;
		this.e11 = m.e11;
		this.e12 = m.e12;
		this.e13 = m.e13;
		this.e20 = m.e20;
		this.e21 = m.e21;
		this.e22 = m.e22;
		this.e23 = m.e23;
		this.e30 = m.e30;
		this.e31 = m.e31;
		this.e32 = m.e32;
		this.e33 = m.e33;
		return this;
	}

	/**
	 * Sets this matrix to the extension of `m` and returns `this`.
	 *
	 * `this.e33` is set to `1` and other components don't exist in `m` are set to `0`.
	 */
	public fromMat3(m: Mat3): Mat4 {
		return this._init(
			m.e00, m.e01, m.e02, 0,
			m.e10, m.e11, m.e12, 0,
			m.e20, m.e21, m.e22, 0,
			0, 0, 0, 1
		);
	}

	public fromRotationAndPos(m: Mat3, v: Vec3) {
		return this._init(
			m.e00, m.e01, m.e02, v.x,
			m.e10, m.e11, m.e12, v.y,
			m.e20, m.e21, m.e22, v.z,
			0, 0, 0, 1
		);
	}

	/**
	 * Sets this matrix to the representation of `transform` and returns `this`.
	 */
	public fromTransform(transform: Transform): Mat4 {
		return this.fromRotationAndPos(transform._rotation, transform._position);
	}

	/**
	 * Returns a clone of the matrix.
	 */
	public clone(): Mat4 {
		return new Mat4(
			this.e00, this.e01, this.e02, this.e03,
			this.e10, this.e11, this.e12, this.e13,
			this.e20, this.e21, this.e22, this.e23,
			this.e30, this.e31, this.e32, this.e33
		);
	}

	/**
	 * Returns the string representation of the matrix.
	 */
	public toString(): String {
		return "Mat4[" + M.toFixed8(this.e00) + ", " + M.toFixed8(this.e01) + ", " + M.toFixed8(this.e02) + ", " + M.toFixed8(this.e03) + ",\n" +
			"    " + M.toFixed8(this.e10) + ", " + M.toFixed8(this.e11) + ", " + M.toFixed8(this.e12) + ", " + M.toFixed8(this.e13) + ",\n" +
			"    " + M.toFixed8(this.e20) + ", " + M.toFixed8(this.e21) + ", " + M.toFixed8(this.e22) + ", " + M.toFixed8(this.e23) + ",\n" +
			"    " + M.toFixed8(this.e30) + ", " + M.toFixed8(this.e31) + ", " + M.toFixed8(this.e32) + ", " + M.toFixed8(this.e33) + "]"
			;
	}

}
